package labox.innovation.gameserver;

import java.util.Collection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.LoggerContext;

import labox.innovation.config.Config;
import labox.innovation.db.core.L2DatabaseFactory;
import labox.innovation.gameserver.instancemanager.ItemsOnGroundManager;
import labox.innovation.gameserver.instancemanager.QuestManager;
import labox.innovation.gameserver.model.ConsignmentManager;
import labox.innovation.gameserver.model.FuzeWorld;
import labox.innovation.gameserver.model.actor.instance.FuzePcInstance;
import labox.innovation.gameserver.network.L2GameClient;
import labox.innovation.gameserver.network.SystemMessageId;
import labox.innovation.gameserver.network.gameserverpackets.ServerStatus;
import labox.innovation.gameserver.network.serverpackets.LeaveWorld;
import labox.innovation.gameserver.network.serverpackets.SystemMessage;
import labox.innovation.gameserver.util.Broadcast;
import labox.innovation.pay.http.RechargeManager;

/**
 * 
 * 这个类提供了关闭和重新启动服务器的功能,它关闭所有打开的客户端连接并保存所有数据。
 * 
 */
public final class Shutdown extends Thread {

	private static Logger _log = LoggerFactory.getLogger(Shutdown.class.getName());

	private static Shutdown _counterInstance = null;

	private int _secondsShut;

	private int _shutdownMode;

	/**
	 * 立即终止
	 */
	public static final int SIGTERM = 0;

	/**
	 * 正常关闭
	 */
	public static final int GM_SHUTDOWN = 1;

	/**
	 * 正常关闭并重启
	 */
	public static final int GM_RESTART = 2;

	/**
	 * 退出关闭任务
	 */
	public static final int ABORT = 3;

	private static final String[] MODE_TEXT = { "SIGTERM", "shutting down", "restarting", "aborting" };

	/**
	 * 
	 * 缺省的构造函数仅仅被内部使用创建一个关闭实例
	 * 
	 */
	private Shutdown() {
		_secondsShut = -1;
		_shutdownMode = SIGTERM;
		_log.info("ShutdownHook registration successfully", Config.SERVER_ID, "关机Hook");
	}

	/**
	 * 创建的关机任务
	 * 
	 * @param seconds
	 *            how many seconds until shutdown
	 * @param restart
	 *            true is the server shall restart after shutdown
	 * 
	 */
	public Shutdown(int seconds, boolean restart) {
		if (seconds < 0) {
			seconds = 0;
		}
		_secondsShut = seconds;
		if (restart) {
			_shutdownMode = GM_RESTART;
		} else {
			_shutdownMode = GM_SHUTDOWN;
		}
	}

	/**
	 * get the shutdown-hook instance the shutdown-hook instance is created by
	 * the first call of this function, but it has to be registrered externaly.
	 * 
	 * @return instance of Shutdown, to be used as shutdown hook
	 */
	public static Shutdown getInstance() {
		return SingletonHolder._instance;
	}

	/**
	 * 发送一个服务器关闭消息给游戏世界里的玩家
	 */
	private void SendServerQuit(int seconds) {
		SystemMessage sysm = new SystemMessage(SystemMessageId.THE_SERVER_WILL_BE_COMING_DOWN_IN_S1_SECONDS);
		sysm.addNumber(seconds);
		Broadcast.toAllOnlinePlayers(sysm);
	}

	/**
	 * 由telent调用的倒计时的关机任务
	 * 
	 * @param IP
	 * @param seconds
	 * @param restart
	 */
	public void startTelnetShutdown(String IP, int seconds, boolean restart) {
		_log.warn("IP: " + IP + " issued shutdown command. " + MODE_TEXT[_shutdownMode] + " in " + seconds + " seconds!", Config.SERVER_ID, "telnet关机任务启动");

		if (restart) {
			_shutdownMode = GM_RESTART;
		} else {
			_shutdownMode = GM_SHUTDOWN;
		}

		if (_shutdownMode > 0) {
			switch (seconds) {
			case 540:// 9分钟
			case 480:// 8分钟
			case 420:// 7分钟
			case 360:// 6 分钟
			case 300:
			case 240:
			case 180:
			case 120:
			case 60:
			case 30:
			case 10:
			case 5:
			case 4:
			case 3:
			case 2:
			case 1:
				break;
			default:
				SendServerQuit(seconds);
			}
		}

		// 如果已经开启了关闭任务，则结束掉
		if (_counterInstance != null) {
			_counterInstance._abort();
		}

		_counterInstance = new Shutdown(seconds, restart);
		_counterInstance.start();
	}

	/**
	 * 由telnet调用的结束一个正在运行的倒计时关闭任务
	 * 
	 * @param IP
	 *            IP Which Issued shutdown command
	 */
	public void telnetAbort(String IP) {
		_log.warn("IP: " + IP + " issued shutdown ABORT. " + MODE_TEXT[_shutdownMode] + " has been stopped!", Config.SERVER_ID, "telnet终止关机任务");

		if (_counterInstance != null) {
			_counterInstance._abort();
			GameServer.gameServer.gph.setAccept(true);
			Announcements _an = Announcements.getInstance();
			_an.announceToAll("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!");
		}
	}

	/**
	 * 在游戏中GM调用的关机任务
	 * 
	 * @param activeChar
	 *            GM who issued the shutdown command
	 * @param seconds
	 *            seconds until shutdown
	 * @param restart
	 *            true if the server will restart after shutdown
	 */
	public void startShutdown(FuzePcInstance activeChar, int seconds, boolean restart) {
		if (restart) {
			_shutdownMode = GM_RESTART;
		} else {
			_shutdownMode = GM_SHUTDOWN;
		}

		_log.warn("GM: " + activeChar.getName() + "(" + activeChar.getObjectId() + ") issued shutdown command. " + MODE_TEXT[_shutdownMode] + " in " + seconds + " seconds!", Config.SERVER_ID, "启动关机任务");

		if (_shutdownMode > 0) {
			switch (seconds) {
			case 540:
			case 480:
			case 420:
			case 360:
			case 300:
			case 240:
			case 180:
			case 120:
			case 60:
			case 30:
			case 10:
			case 5:
			case 4:
			case 3:
			case 2:
			case 1:
				break;
			default:
				SendServerQuit(seconds);
			}
		}

		if (_counterInstance != null) {
			_counterInstance._abort();
		}

		// the main instance should only run for shutdown hook, so we start a
		// new instance
		_counterInstance = new Shutdown(seconds, restart);
		_counterInstance.start();
	}

	/**
	 * 由游戏中GM调用的推出关闭任务
	 * 
	 * @param activeChar
	 *            GM who issued the abort command
	 */
	public void abort(FuzePcInstance activeChar) {
		_log.warn("GM: " + activeChar.getName() + "(" + activeChar.getObjectId() + ") issued shutdown ABORT. " + MODE_TEXT[_shutdownMode] + " has been stopped!", Config.SERVER_ID, "退出关闭任务");
		if (_counterInstance != null) {
			_counterInstance._abort();
			GameServer.gameServer.gph.setAccept(true);
			Announcements _an = Announcements.getInstance();
			_an.announceToAll("Server aborts " + MODE_TEXT[_shutdownMode] + " and continues normal operation!");
		}
	}

	/**
	 * set the shutdown mode
	 * 
	 * @param mode
	 *            what mode shall be set
	 */
	private void setMode(int mode) {
		_shutdownMode = mode;
	}

	/**
	 * set shutdown mode to ABORT
	 * 
	 */
	private void _abort() {
		_shutdownMode = ABORT;
	}

	/**
	 * 计算倒计时并报告给所有的玩家倒计时的情况
	 */
	private void countdown() {
		try {
			while (_secondsShut > 0) {
				switch (_secondsShut) {
				case 540:
					SendServerQuit(540);
					break;
				case 480:
					SendServerQuit(480);
					break;
				case 420:
					SendServerQuit(420);
					break;
				case 360:
					SendServerQuit(360);
					break;
				case 300:
					SendServerQuit(300);
					break;
				case 240:
					SendServerQuit(240);
					break;
				case 180:
					SendServerQuit(180);
					break;
				case 120:
					SendServerQuit(120);
					break;
				case 60:// 关机剩下60秒的时候不能在连接服务器
					LoginServerThread.getInstance().setServerStatus(ServerStatus.STATUS_DOWN); // 避免新的玩家登陆,与登录服务器的socket连接不关闭
					GameServer.gameServer.gph.setAccept(false);
					SendServerQuit(60);
					break;
				case 30:
					SendServerQuit(30);
					break;
				case 10:
					SendServerQuit(10);
					break;
				case 5:
					SendServerQuit(5);
					break;
				case 4:
					SendServerQuit(4);
					break;
				case 3:
					SendServerQuit(3);
					break;
				case 2:
					SendServerQuit(2);
					break;
				case 1:
					SendServerQuit(1);
					break;
				}

				_secondsShut--;

				int delay = 1000; // milliseconds
				Thread.sleep(delay);
				if (_shutdownMode == ABORT)
					break;
			}
		} catch (InterruptedException e) {
			// this will never happen
		}
	}

	/**
	 * this function is called, when a new thread starts
	 * 
	 * if this thread is the thread of getInstance, then this is the shutdown
	 * hook and we save all data and disconnect all clients.
	 * 
	 * after this thread ends, the server will completely exit
	 * 
	 * if this is not the thread of getInstance, then this is a countdown
	 * thread. we start the countdown, and when we finished it, and it was not
	 * aborted, we tell the shutdown-hook why we call exit, and then call exit
	 * 
	 * when the exit status of the server is 1, startServer.sh / startServer.bat
	 * will restart the server.
	 * 
	 */
	@Override
	public void run() {
		if (this == SingletonHolder._instance) {// 由java虚拟机启动的关闭序列任务
			// 关闭主逻辑线程
			Engine.getInstance().close();
			try {
				// 等待主逻辑线程关闭
				Engine.getInstance().join();
			} catch (InterruptedException e) {
			}
			close();
		} else {
			// 进入倒计时状态
			countdown();
			// 进行最后的日志记录
			_log.warn("GM shutdown countdown is over. " + MODE_TEXT[_shutdownMode] + " NOW!", Config.SERVER_ID, "关机");

			switch (_shutdownMode) {
			case GM_SHUTDOWN:
				SingletonHolder._instance.setMode(GM_SHUTDOWN);
				System.exit(0);// 通知java虚拟机启动关闭序列任务
				break;
			case GM_RESTART:
				SingletonHolder._instance.setMode(GM_RESTART);
				System.exit(2);
				break;
			}
		}
	}

	private void close() {

		// 再次确保不再有客户端的开始新的连接
		GameServer.gameServer.gph.setAccept(false);
		// 关闭充值系统
		RechargeManager.stop();

		// 关闭与登陆服务的连接通讯
		try {
			LoginServerThread.getInstance().close();
		} catch (Throwable t) {
			t.printStackTrace();
		}

		// 关闭所有的线程池，防止其他的操作进行数据库回写
		try {
			ThreadPoolManager.getInstance().shutdown();
		} catch (Throwable t) {
			t.printStackTrace();
		}

		while (!ThreadPoolManager.getInstance().isShutdown()) {
			try {// 等待3秒
				Thread.sleep(3000L);
			} catch (InterruptedException e) {
			}
		}

		// 保存所有的数据
		saveData();

		// 关闭selector，关闭所有的socket的连接
		try {
			GameServer.gameServer.getSelectorThread().shutdown();
			GameServer.gameServer.getSelectorThread().setDaemon(true);
		} catch (Throwable t) {
			// ignore
		}

		// 关闭数据库连接池
		try {
			L2DatabaseFactory.getInstance().shutdown();
		} catch (Throwable t) {
			t.printStackTrace();
		}

		// 关闭日志链接socket防止丢失日志
		LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
		lc.stop();

		// 终止关闭队列，虚拟机退出
		if (SingletonHolder._instance._shutdownMode == GM_RESTART) {
			Runtime.getRuntime().halt(2);
		} else {
			Runtime.getRuntime().halt(0);// 关闭虚拟机的关闭队列,终止虚拟机
		}
	}

	/**
	 * this sends a last byebye, disconnects all players and saves data
	 * 
	 */
	private void saveData() {
		switch (_shutdownMode) {
		case SIGTERM:
			_log.info("SIGTERM received. Shutting down NOW!", Config.SERVER_ID, "关机模式");
			break;
		case GM_SHUTDOWN:
			_log.info("GM shutdown received. Shutting down NOW!", Config.SERVER_ID, "关机模式");
			break;
		case GM_RESTART:
			_log.info("GM restart received. Restarting NOW!", Config.SERVER_ID, "关机模式");
			break;
		}

		// 回写所有人的数据，并关闭所有的socket连接
		disconnectAllCharacters();

		// Save all global (non-player specific) Quest data that needs to
		// persist after reboot
		QuestManager.getInstance().save();

		// 保存所有的寄售物品
		ConsignmentManager.save();

		// Save items on ground before closing
		if (Config.SAVE_DROPPED_ITEM) {
			ItemsOnGroundManager.getInstance().saveInDb();
			ItemsOnGroundManager.getInstance().cleanUp();
			_log.info("ItemsOnGroundManager: All items on ground saved!!", Config.SERVER_ID, "地图道具管理器");
		}
		_log.info("Data saved. All players disconnected, shutting down.", Config.SERVER_ID, "数据存储结束");

		try {
			Thread.sleep(5000L);
		} catch (InterruptedException e) {
		}
	}

	/**
	 * 保存所有玩家的数据并把所有的客户端从服务器上断开连接
	 * 
	 */
	private void disconnectAllCharacters() {
		Collection<FuzePcInstance> pls = FuzeWorld.getInstance().getAllPlayers().values();
		// 回写所有的玩家数据
		for (FuzePcInstance player : pls) {
			L2GameClient.saveCharToDisk(player);
		}

		try {
			Thread.sleep(1000L);
		} catch (Throwable t) {
		}

		// 关闭所有人的连接
		for (FuzePcInstance player : pls) {
			try {
				player.getClient().close(LeaveWorld.LEAVEWORLD);
			} catch (Throwable t) {
			}
		}
	}

	@SuppressWarnings("synthetic-access")
	private static class SingletonHolder {
		protected static final Shutdown _instance = new Shutdown();
	}
}
